iOS多线程 - 线程间的通信

因为线程是串行的,这意味着如果我们要下载一个图片,使它呈现在一个UIImageView 里,那么这个线程必须要等待图片下载完成后,才能显示出来。为了解决这样的问题,我们需要用到多线程的技术,将下载图片交给子线程完成,将加载图片到 UIImageView 交给主线程,这就涉及到一个问题,就是线程之间的通信。

线程通讯的两种方式

  1. 从一个线程给另一个线程传递数据。
  2. 在一个线程中执行完特定任务后,转到另一个线程继续执行。

以上面的问题举例,我们要在一个线程中下载图片,然后要另一个线程展示图片,苹果官方推荐,刷新界面的操作最好在主线程中完成,那么我们来实验一下,如果将展示图片的操作交给子线程,会发生什么。首先我们创建一个 UIImageView 并将它添加到 UIViewController,然后我们创建一个子线程:

1
2
let subThread = NSThread(target: self, selector: #selector(ViewController.run(_:)), object: "我是传入参数")
subThread.start()

下面我们创建 run 这个线程执行方法,写入以下代码下载图片:

1
2
3
4
let url: NSURL = NSURL(string: "http://pic.qiantucdn.com/58pic/19/40/65/17m58PICgSH_1024.jpg")! //使用 NSURL 创建下载链接
let data: NSData = NSData(contentsOfURL: url)! //使用 NSData 获得文件
let img: UIImage = UIImage(data: data)! //使用 init?(data: NSData) 实例化图片

最后将图片加载到 UIImageView 里:

1
self.myImg.image = img

现在我们运行这个程序,如果你的 XCode 版本是新的话,那么可能会收到一条报错:

1
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.

这个原因是 iOS9 中新增了 App Transport Security(简称ATS)特性,在 iOS9 以前请求网络的时候都用到的HTTP,现在转向TLS1.2协议进行传输。这也意味着所有的 HTTP 协议都强制使用了 HTTPS 协议进行传输,所以系统会告诉我们不能直接使用HTTP进行请求,这需要在 Info.plist 新增一段用于控制ATS的配置:

1
2
3
4
5
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

我们重新运行这个程序,相信这时图片已经成功加载出来了,但是控制台会收到这样一句提示:

1
This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release.

这是说,我们使用了 background thread 后台进程更改了布局,这可能会导致崩溃和其他问题,这也符合苹果官方所推荐的:刷新界面的操作最好在主线程中完成

那么现在我们尝试解决这个问题,将刷新界面的操作交给主线程操作,相信大家已经意识到问题的核心,如何通知主线程刷新界面 ?我们来调用这个方法:

1
performSelectorOnMainThread(#selector(ViewController.showImage(_:)), withObject: img, waitUntilDone: true)

下面我们先看一下这个方法的参数:

形参 作用 填写
aSelector: Selector 这是一个选择器,代表要执行的方法。 自己写的方法
withObject arg: AnyObject? 这是执行方法的传入参数。 可以是nil
waitUntilDone wait: Bool 是否要等待执行方法的完成。 可以是false

前两个参数比较好理解,因为执行方法也是需要时间的,第三个参数 truefalse 的区别是:如果是 true 的话,主线程会等待执行方法完成,主线程才会继续其他的操作;如是是 false 的话,主线程马上就会结束,并不关心执行方法是否完成。

如果你有一些限制,比如要等执行方法完成之后,在执行一些其他的工作,这是就输入 true,如果你不关心执行的结果,就输入 false

下面我们实现让主线程操作 UIImageView 显示图片的方法:

1
2
3
func showImage(img: UIImage) {
self.myImg.image = img
}

再次运行,风险提示没有了,这说明我们实现了,从子线程中下载图片,并调用主线程,在主线程中刷新界面这样一个功能。